package com.book.manager.oauth2;

import com.alibaba.fastjson.JSON;

import com.book.manager.base.result.CodeMsgEnum;
import com.book.manager.base.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

/**
 * @author Zhifeng.Zeng
 * @description 自定义未授权 token无效 权限不足返回信息处理类
 * @date 2019/3/4 15:49
 */
@Component
@Slf4j
public class CustomAuthExceptionHandler implements AuthenticationEntryPoint, AccessDeniedHandler {

    @Value("${server.servlet.context-path}")
    private String contextPath;
    @Autowired
    RestTemplate restTemplate;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ServerConfig serverConfig;
    @Autowired
    TokenStore tokenStore;
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        Throwable cause = authException.getCause();
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        // CORS "pre-flight" request
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        response.addHeader("Access-Control-Max-Age", "1800");

        // ================
        if (!StringUtils.isEmpty(request.getHeader("Authorization"))) {
            String accessToken = request.getHeader("Authorization").replace("Bearer ", "");
            if (!StringUtils.isEmpty(accessToken)) {
                String refreshToken = stringRedisTemplate.opsForValue().get(String.format("access_to_refresh:%s",
                        accessToken));
                if (!StringUtils.isEmpty(refreshToken)) {

                    MultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
                    formData.add("client_id", "gsjtzn-oa-server");
                    formData.add("client_secret", "gsjtzn-oa-server");
                    formData.add("grant_type", "refresh_token");
                    formData.add("refresh_token", refreshToken);

                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
                    Map map = restTemplate.exchange(serverConfig.getUrl() + "/oauth/token", HttpMethod.POST,
                            new HttpEntity<MultiValueMap<String, String>>(formData, headers), Map.class).getBody();
                    //如果刷新异常,则坐进一步处理
                    if (map.get("error") != null) {
                        // 返回指定格式的错误信息
                        response.setStatus(401);
                        response.setHeader("Content-Type", "application/json;charset=utf-8");
                        response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
                        response.getWriter().flush();
                        //如果是网页,跳转到登陆页面
                        //response.sendRedirect("login");
                    } else {
                        String uriString = request.getRequestURI();
                        if (!StringUtils.isEmpty(uriString)) {
                            if (uriString.startsWith(contextPath)) {
                                uriString = uriString.substring(contextPath.length());
                            }


                            // 刷新后，需要设置authentication,否则下一步的转发，获取不到用户名称
                            tokenStore.readAccessToken(refreshToken);
                            OAuth2RefreshToken oAuth2RefreshToken = tokenStore.readRefreshToken(refreshToken);
                            if (refreshToken == null) {
                                throw new InvalidGrantException("Invalid refresh token: " + oAuth2RefreshToken);
                            }
                            OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(oAuth2RefreshToken);
                            SecurityContextHolder.getContext().setAuthentication(authentication);

                            request.getRequestDispatcher(uriString).forward(request, response);
                        }
                        return;
                    }
                }
            }
        }
        // =======
        if (cause instanceof InvalidTokenException) {
            log.error("InvalidTokenException : {}", cause.getMessage());
            //Token无效
            response.getWriter().write(JSON.toJSONString(Result.of(CodeMsgEnum.ACCESS_TOKEN_INVALID)));
        } else {
            log.error("AuthenticationException : NoAuthentication");
            //资源未授权
            response.getWriter().write(JSON.toJSONString(Result.of(CodeMsgEnum.UNAUTHORIZED)));
        }

    }

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpServletResponse.SC_OK);
        response.addHeader("Access-Control-Allow-Origin", "*");
        response.addHeader("Cache-Control", "no-cache");
        response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        response.addHeader("Access-Control-Max-Age", "1800");
        //访问资源的用户权限不足
        log.error("AccessDeniedException : {}", accessDeniedException.getMessage());
        response.getWriter().write(JSON.toJSONString(Result.of(CodeMsgEnum.INSUFFICIENT_PERMISSIONS)));
    }
}
